#include "gtest/gtest.h"

#include "ObjectTest.h"
#include "helper/ScopedMemPtr.h"
#include <functional>

class HashTest : public ObjectTest {
public:
    void SetUp() override {
        ObjectTest::SetUp();
        ASSERT_TRUE(getObjectFactory() != nullptr);
        hash = getObjectFactory()->createHash();
    }
    void TearDown() override {
        delete hash;
        ObjectTest::TearDown();
    }
    helper::object::Hash *  getHash() {
        return hash;
    }

protected:
    template<typename T, typename Getter>
    void testRawTypeInsertAndGet(const T & expect, const Getter & getter){
        const char * key = "rawType";
        ASSERT_EQ(0, getHash()->insert(key, expect));

        auto ret = getter(key); 
        ASSERT_TRUE(ret.first);
        ASSERT_EQ(expect, ret.second);

        getHash()->clear();

        //no exist
        ASSERT_EQ(0ul, getHash()->size());
        ASSERT_FALSE(getter(key).first);
    }
    template<typename Inserter>
    void __testInsert(const char * expect, const char * key, const Inserter & inserter){
        ASSERT_EQ(0, inserter());
        ASSERT_EQ(1ul, getHash()->size());
        ASSERT_EQ(true, getHash()->getStdString(key).first);
        ASSERT_EQ(expect, getHash()->getStdString(key).second);

        getHash()->clear();

        ASSERT_EQ(0ul, getHash()->size());
        ASSERT_EQ(false, getHash()->getStdString(key).first);

    }
    template<typename T, typename Getter>
    void testComposedTypeGetAndInsert(T * obj, const Getter & getter){
        const char * key = "object";
        ASSERT_EQ(0, getHash()->insert(key, obj));
        T * value = getter(getHash(), key);
        ASSERT_TRUE(value != nullptr);
        ASSERT_EQ(obj, value);

        getHash()->clear();

        //not exist 
        ASSERT_EQ(0ul, getHash()->size());
        ASSERT_TRUE(nullptr == getter(getHash(), key));
    }
private:
    helper::object::Hash * hash;
};

using namespace helper;
using namespace object;

//TODO:Object->getFactory(); 
//Object equals ????
//TEST_F(HashTest, testGetObjectFactory){
//    auto pObj = getObjectFactory()->createHash();
//    ASSERT_EQ(getObjectFactory == pObj->getFactory());
//}
TEST_F(HashTest, testCreate){
    auto  pObj = getObjectFactory()->createHash();
    ASSERT_TRUE(nullptr != pObj);
}

TEST_F(HashTest, testInsertAndRemove){
    getHash()->insert("hello", "world");
    ASSERT_EQ(1ul, getHash()->size());

    auto * value = getHash()->remove("hello");
    ASSERT_EQ(0ul, getHash()->size());

    delete value;
}

TEST_F(HashTest, testTraversal){
    const char * expectValue = "value";
    getHash()->insert("key", expectValue);
    ASSERT_EQ(1ul, getHash()->size());

    String*  expectString = getObjectFactory()->createString(expectValue);
    getHash()->insert("key1", expectString);

    ASSERT_EQ(2ul, getHash()->size());

    auto it = getHash()->iterator();
    while(it->hasNext()) {
        auto & valueRef = it->next();
        ASSERT_EQ(expectString->toString(), valueRef.second->toString());
    }
}

TEST_F(HashTest, testNextOutOfRange){
    ObjectTest::testNextOutOfRange(getHash());
}

TEST_F(HashTest, testClear){
    getHash()->insert("hello", "world");
    ASSERT_EQ(1ul, getHash()->size());
    
    ObjectTest::testClear(getHash());
}


TEST_F(HashTest, testInsertAndGet){
    testRawTypeInsertAndGet<char>('1', [this](const char * key){ 
            return this->getHash()->getChar(key); 
         });
    testRawTypeInsertAndGet<int>(100, [this](const char * key){
            return this->getHash()->getInt(key);
        });
    testRawTypeInsertAndGet<long long>(100l, [this](const char * key){
            return this->getHash()->getInt(key);
        });

    testRawTypeInsertAndGet<double>(100.0, [this](const char * key){
            return this->getHash()->getDouble(key);
        });

    testRawTypeInsertAndGet<std::string>("hello", [this](const char * key){
            return this->getHash()->getStdString(key);
        });
}


TEST_F(HashTest, testInsertStrings){
    const char * expect = "hello";
    const char * key = "stringkey";
    {
        auto inserter = [this, expect, key](){
            return this->getHash()->insert(key, expect, strlen(expect));
        };
        __testInsert(expect, key, inserter);
    }
    {
        auto inserter = [this, expect, key](){
            return this->getHash()->insert(key, expect, 0, strlen(expect));
        };
        __testInsert(expect, key, inserter);
    }
    {
        const char * expect = "cccccc";
        auto inserter = [this, expect, key](){
            return this->getHash()->insert(key, strlen(expect), 'c');
        };
        __testInsert(expect, key, inserter);
    }
    {
        auto inserter = [this, expect, key](){
            return getHash()->insert(key, std::string(expect));
        };
        __testInsert(expect, key, inserter);
    }
}

TEST_F(HashTest, testStringGetAndInsert){
    auto getter = [](Hash * hash, const char * key) {
        return hash->getString(key);
    };
    testComposedTypeGetAndInsert(getObjectFactory()->createString("hello"), getter);
}
TEST_F(HashTest, testArrayGetAndInsert){
    auto getter = [](Hash * hash, const char * key) {
        return hash->getArray(key);
    };
    testComposedTypeGetAndInsert(getObjectFactory()->createArray(), getter);
}
TEST_F(HashTest, testHashGetAndInsert){
    auto getter = [](Hash * hash, const char * key) {
        return hash->getHash(key);
    };
    testComposedTypeGetAndInsert(getObjectFactory()->createHash(), getter);
}

